# coding:utf-8
import pandas as pd
import numpy as np


def pair_combine_feature(df, cal_func, func_name=None, ignore_columns=[], return_way='add', reverse_cal=True, self_cal=True):
    '''
    每两列特征 -> 组合为一个新特征。默认计算所有的(column_i, column_j)，m^2个组合

    输入参数：

        cal_func：形如 func(x, y) -> z的函数 x,y,z都是series【要求保持index，尽量使用np中的操作符进行操作】。
        func_name：字符串，表示函数名称，为None则使用cal_func.__name__
        ignore_columns: 这里的columns不参与计算。如y列。
        return_way：组合后的结果如何输出。add表示直接加到df上。new就是输出新的。
        reverse_cal：是否计算 i > j的。如果cal_func为对称的则没有必要
        self_cal：是否计算i == j的。
        默认计算所有的(column_i, column_j)，m^2个组合

    输出：
        return_way == 'add' ： 输出添加了特征的df。
        return_way == 'new'：只输出新的组合特征df。

    '''
    if func_name is None:
        func_name = cal_func.__name__

    result = {}
    assert (df.columns.duplicated() == False).all()
    for i in range(len(df.columns)):
        for j in range(len(df.columns)):
            if (df.columns[i] in ignore_columns or df.columns[j] in ignore_columns):
                continue
            if (i == j) and (self_cal is False):
                continue
            if (i > j) and (reverse_cal is False):
                continue
            column_name = '%s %s %s' % (df.columns[i], func_name, df.columns[j])
            result[column_name] = cal_func(df[df.columns[i]], df[df.columns[j]])

    result = pd.DataFrame(result)

    assert result.index.tolist() == df.index.tolist()  # 不要破坏index

    if return_way == 'add':
        return pd.concat([df, result], axis=1)
    else:
        return result


def pair_combine_feature_multi_func(df, cal_func_list, func_name_list=None, ignore_columns_list=[], return_way='add', reverse_cal_list=True, self_cal_list=True):
    '''
    每两列特征 -> 组合为一个新特征。默认计算所有的(column_i, column_j)，m^2个组合。
    cal_func是一个list，可以输入多个func进行计算。

    输入参数：

        cal_func：[func1, func2]。func(x, y) -> z， x,y,z都是series【要求保持index，尽量使用np中的操作符进行操作】。
        func_name_list：字符串list，表示函数名称，为None则使用cal_func.__name__
        ignore_columns_list: 这里的columns不参与计算。如y列。如使用['target', 'target']。**注意：多个相同的也要多次输入**
        return_way：组合后的结果如何输出。add表示直接加到df上。new就是输出新的。
        reverse_cal_list：是否计算 i > j的。如果cal_func为对称的则没有必要
        self_cal_list：是否计算i == j的。
        默认计算所有的(column_i, column_j)，m^2个组合

    输出：
        return_way == 'add' ： 输出添加了特征的df。
        return_way == 'new'：只输出新的组合特征df。

    示例：
    pair_combine_feature_multi_func(
                df, 
                [np.add, np.multiply], 
                reverse_cal_list=[False, False], 
                self_cal_list=[False, True], 
                return_way='new', 
                ignore_columns_list=[['target'], ['target']],
    )

    '''
    if func_name_list is None:
        func_name_list = [None] * len(cal_func_list)
    for i in range(len(cal_func_list)):
        if func_name_list[i] is None:
            func_name_list[i] = cal_func_list[i].__name__
    assert len(set(cal_func_list)) == len(cal_func_list)
    assert len(set(func_name_list)) == len(func_name_list)

    result = {}
    assert (df.columns.duplicated() == False).all()
    for k in range(len(cal_func_list)):
        for i in range(len(df.columns)):
            for j in range(len(df.columns)):
                if (df.columns[i] in ignore_columns_list[k] or df.columns[j] in ignore_columns_list[k]):
                    continue
                if (i == j) and (self_cal_list[k] is False):
                    continue
                if (i > j) and (reverse_cal_list[k] is False):
                    continue
                column_name = '%s %s %s' % (df.columns[i], func_name_list[k], df.columns[j])
                result[column_name] = cal_func_list[k](df[df.columns[i]], df[df.columns[j]])

    result = pd.DataFrame(result)

    assert result.index.tolist() == df.index.tolist()  # 不要破坏index

    if return_way == 'add':
        return pd.concat([df, result], axis=1)
    else:
        return result
